package ca.scotthyndman.as2haxe.dom;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import ca.scotthyndman.as2haxe.dom.visitor.IVisitable;

/**
 * Abstract superclass of all Abstract Syntax Tree (AST) node types.
 */
public abstract class ASTNode implements ICodeRegion, IVisitable {

	/**
	 * Owning AST.
	 * <p>
	 * N.B. This ia a private field, but declared as package-visible for more
	 * efficient access from inner classes.
	 * </p>
	 */
	final AST ast;

	/**
	 * Parent AST node, or <code>null</code> if this node is a root. Initially
	 * <code>null</code>.
	 */
	private ASTNode parent = null;

	/**
	 * Describes the location of this node in it's parent.
	 */
	private ASPropertyDescriptor location = null;

	/**
	 * The ast node's position in the source code.
	 */
	private ASRegion fullRegion;

	/**
	 * <code>true</code> if the node cannot be modified.
	 */
	private boolean protect = false;

	/**
	 * Additional information about this node, used by the conversion classes.
	 */
	private Properties extraInfo = new Properties();

	// ====== CONSTRUCTION

	/**
	 * Creates a new AST node owned by the given AST. Once established, the
	 * relationship between an AST node and its owning AST does not change over
	 * the lifetime of the node. The new node has no parent node, and no
	 * properties.
	 * <p>
	 * N.B. This constructor is package-private; all subclasses my be declared
	 * in the same package; clients are unable to declare additional subclasses.
	 * </p>
	 * 
	 * @param ast
	 *            the AST that is to own this node
	 */
	ASTNode(AST ast) {
		if (ast == null) {
			throw new IllegalArgumentException();
		}

		this.ast = ast;
	}

	// ======= PROPERTIES

	/**
	 * Returns this node's AST.
	 * <p>
	 * Note that the relationship between an AST node and its owing AST does not
	 * change over the lifetime of a node.
	 * </p>
	 * 
	 * @return the AST that owns this node
	 */
	public final AST getAST() {
		return this.ast;
	}

	/**
	 * Returns this node's parent node, or <code>null</code> if this is the
	 * root node.
	 * <p>
	 * Note that the relationship between an AST node and its parent node may
	 * change over the lifetime of a node.
	 * </p>
	 * 
	 * @return the parent of this node, or <code>null</code> if none
	 */
	public ASTNode getParent() {
		return this.parent;
	}

	/**
	 * Returns the location of this node within its parent, or <code>null</code>
	 * if this is a root node.
	 * 
	 * <p>
	 * Note that the relationship between an AST node and its parent node may
	 * change over the lifetime of a node.
	 * </p>
	 * 
	 * @return the location of this node in its parent, or <code>null</code>
	 *         if this node has no parent
	 */
	public final ASPropertyDescriptor getLocationInParent() {
		return this.location;
	}

	/**
	 * Sets or clears this node's parent node and location.
	 * <p>
	 * Note that this method is package-private. The pointer from a node to its
	 * parent is set implicitly as a side effect of inserting or removing the
	 * node as a child of another node. This method calls
	 * <code>ast.modifying()</code>.
	 * </p>
	 * 
	 * @param parent
	 *            the new parent of this node, or <code>null</code> if none
	 * @param property
	 *            the location of this node in its parent, or <code>null</code>
	 *            if <code>parent</code> is <code>null</code>
	 * @see #getLocationInParent
	 * @see #getParent
	 */
	void setParent(ASTNode parent, ASPropertyDescriptor property) {
		this.parent = parent;
		this.location = property;
	}

	/**
	 * Removes this node from its parent. Has no effect if this node is
	 * unparented. If this node appears as an element of a child list property
	 * of its parent, then this node is removed from the list using
	 * <code>List.remove</code>. If this node appears as the value of a child
	 * property of its parent, then this node is detached from its parent by
	 * passing <code>null</code> to the appropriate setter method; this
	 * operation fails if this node is in a mandatory property.
	 * 
	 * @since 3.0
	 */
	public final void delete() {
		ASPropertyDescriptor p = getLocationInParent();
		if (p == null) {
			// node is unparented
			return;
		}
		if (p instanceof ASChildPropertyDescriptor) {
			getParent().setStructuralProperty(this.location, null);
			return;
		}
		if (p instanceof ASChildListPropertyDescriptor) {
			List l = (List) getParent().getStructuralProperty(this.location);
			l.remove(this);
		}
	}

	/**
	 * Returns the root node at or above this node; returns this node if it is a
	 * root.
	 * 
	 * @return the root node at or above this node
	 */
	public final ASTNode getRoot() {
		ASTNode candidate = this;
		while (true) {
			ASTNode p = candidate.getParent();
			if (p == null) {
				// candidate has no parent - that's the guy
				return candidate;
			}
			candidate = p;
		}
	}

	/**
	 * Returns the nearest scoped element to this node.
	 * 
	 * @return the scope, or <code>null</code>
	 */
	public IScopeElement getNearestScope() {
		ASTNode candidate = this.parent;
		while (candidate != null) {
			if (candidate instanceof IScopeElement) {
				return (IScopeElement) candidate;
			}
			candidate = candidate.getParent();
		}

		return null;
	}

	/**
	 * Gets the first ancestor of the given class
	 * 
	 * @return the first ancestor of type <code>ancestorClass</code>
	 */
	public ASTNode getAncestor(Class<? extends ASTNode> ancestorClass) {
		ASTNode candidate = this.parent;
		while (candidate != null) {
			if (ancestorClass.isInstance(candidate)) {
				return candidate;
			}
			candidate = candidate.getParent();
		}

		return null;
	}

	/**
	 * Returns the node's region.
	 */
	public ASRegion getFullRegion() {
		return fullRegion;
	}

	/**
	 * Sets the node's region.
	 */
	public void setFullRegion(ASRegion fullRegion) {
		this.fullRegion = fullRegion;
	}

	/**
	 * Returns a properties object containing additional information about this
	 * node, as used by the conversion tools.
	 * 
	 * @return the properties of this node
	 */
	public Properties getExtraInfo() {
		return extraInfo;
	}

	/**
	 * Returns whether this node can be modified.
	 * 
	 * @return <code>true</code> if this node is protected
	 */
	public boolean isProtected() {
		return protect;
	}

	/**
	 * Sets whether this node can be modified.
	 * 
	 * @param flag
	 *            <code>true</code> if this node is protected from
	 *            modification
	 */
	public void setProtected(boolean flag) {
		this.protect = flag;
	}

	// ====== STRUCTURAL PROPERTIES

	/**
	 * Checks whether the given new child node is a node in a different AST from
	 * its parent-to-be, whether it is already has a parent, whether adding it
	 * to its parent-to-be would create a cycle, and whether the child is of the
	 * right type. The parent-to-be is the enclosing instance.
	 * 
	 * @param node
	 *            the parent-to-be node
	 * @param newChild
	 *            the new child of the parent
	 * @param cycleCheck
	 *            <code>true</code> if cycles are possible and need to be
	 *            checked, <code>false</code> if cycles are impossible and do
	 *            not need to be checked
	 * @param nodeType
	 *            a type constraint on child nodes, or <code>null</code> if no
	 *            special check is required
	 * @exception IllegalArgumentException
	 *                if:
	 *                <ul>
	 *                <li>the child is null</li>
	 *                <li>the node belongs to a different AST</li>
	 *                <li>the child has the incorrect node type</li>
	 *                <li>the node already has a parent</li>
	 *                </ul>
	 */
	static void checkNewChild(ASTNode node, ASTNode newChild,
			Class<? extends ASTNode> nodeType) {
		if (newChild.ast != node.ast) {
			// new child is from a different AST
			throw new IllegalArgumentException();
		}
		if (newChild.getParent() != null) {
			// new child currently has a different parent
			throw new IllegalArgumentException();
		}
		Class<? extends ASTNode> childClass = newChild.getClass();
		if (nodeType != null && !nodeType.isAssignableFrom(childClass)) {
			// new child is not of the right type
			throw new ClassCastException();
		}
		if (newChild.isProtected()) {
			// new child node is protected => cannot be parented
			throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
		}
	}

	/**
	 * Prelude portion of the "3 step program" for replacing the old child of
	 * this node with another node. Here is the code pattern found in all AST
	 * node subclasses:
	 * 
	 * <pre>
	 * ASTNode oldChild = this.foo;
	 * preReplaceChild(oldChild, newFoo, FOO_PROPERTY);
	 * this.foo = newFoo;
	 * postReplaceChild(oldChild, newFoo, FOO_PROPERTY);
	 * </pre>
	 * 
	 * @param oldChild
	 *            the old child of this node, or <code>null</code> if there
	 *            was no old child to replace
	 * @param newChild
	 *            the new child of this node, or <code>null</code> if there is
	 *            no replacement child
	 * @param property
	 *            the property descriptor of this node describing the
	 *            relationship between node and child
	 * @exception RuntimeException
	 *                if:
	 *                <ul>
	 *                <li>the node belongs to a different AST</li>
	 *                <li>the node already has a parent</li>
	 *                <li>a cycle in would be created</li>
	 *                <li>any of the nodes involved are unmodifiable</li>
	 *                </ul>
	 * @since 3.0
	 */
	final void preReplaceChild(ASTNode oldChild, ASTNode newChild,
			ASChildPropertyDescriptor property) {
		if (this.isProtected()) {
			// this node is protected => cannot gain or lose children
			throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
		}
		if (newChild != null) {
			checkNewChild(this, newChild, null);
		}
		// delink old child from parent
		if (oldChild != null) {
			if (oldChild.isProtected()) {
				// old child node is protected => cannot be unparented
				throw new IllegalArgumentException(
						"AST node cannot be modified"); //$NON-NLS-1$
			}
			if (newChild != null) {
				this.ast.preReplaceChildEvent(this, oldChild, newChild,
						property);
			} else {
				this.ast.preRemoveChildEvent(this, oldChild, property);
			}
			oldChild.setParent(null, null);
		} else {
			if (newChild != null) {
				this.ast.preAddChildEvent(this, newChild, property);
			}
		}
		// link new child to parent
		if (newChild != null) {
			newChild.setParent(this, property);
			// cannot notify postAddChildEvent until parent is linked to child
			// too
		}
	}

	/**
	 * Postlude portion of the "3 step program" for replacing the old child of
	 * this node with another node. See
	 * {@link #preReplaceChild(ASTNode, ASTNode, ASChildPropertyDescriptor)} for
	 * details.
	 */
	final void postReplaceChild(ASTNode oldChild, ASTNode newChild,
			ASChildPropertyDescriptor property) {
		// link new child to parent
		if (newChild != null) {
			if (oldChild != null) {
				this.ast.postReplaceChildEvent(this, oldChild, newChild,
						property);
			} else {
				this.ast.postAddChildEvent(this, newChild, property);
			}
		} else {
			this.ast.postRemoveChildEvent(this, oldChild, property);
		}
	}

	/**
	 * Prelude portion of the "3 step program" for changing the value of a
	 * simple property of this node. Here is the code pattern found in all AST
	 * node subclasses:
	 * 
	 * <pre>
	 * preValueChange(FOO_PROPERTY);
	 * this.foo = newFoo;
	 * postValueChange(FOO_PROPERTY);
	 * </pre>
	 * 
	 * The first part (preValueChange) does the precondition check to make sure
	 * the node is modifiable (not PROTECTED). The change operation must fail
	 * atomically; so it is crucial that the precondition checks are done before
	 * the field is hammered. The final part (postValueChange)reports
	 * post-change events.
	 * <p>
	 * This method calls <code>ast.modifying()</code> for the node affected.
	 * </p>
	 * 
	 * @param property
	 *            the property descriptor of this node
	 * @exception RuntimeException
	 *                if:
	 *                <ul>
	 *                <li>this node is unmodifiable</li>
	 *                </ul>
	 * @since 3.0
	 */
	final void preValueChange(ASSimplePropertyDescriptor property) {
		if (this.protect) {
			// this node is protected => cannot change valure of properties
			throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
		}
		this.ast.preValueChangeEvent(this, property);
	}

	/**
	 * Postlude portion of the "3 step program" for replacing the old child of
	 * this node with another node. See
	 * {@link #preValueChange(SimplePropertyDescriptor)} for details.
	 * 
	 * @since 3.0
	 */
	final void postValueChange(ASSimplePropertyDescriptor property) {
		this.ast.postValueChangeEvent(this, property);
	}

	/**
	 * Returns the value of the given structural property for this node. The
	 * value returned depends on the kind of property:
	 * <ul>
	 * <li>{@link ASChildPropertyDescriptor} - the child node (type
	 * <code>ASTNode</code>), or <code>null</code> if none</li>
	 * <li>{@link ASChildListPropertyDescriptor} - the list (element type:
	 * {@link ASTNode})</li>
	 * </ul>
	 * 
	 * @param property
	 *            the property
	 * @return the value, or <code>null</code> if none
	 * @exception RuntimeException
	 *                if this node does not have the given property
	 */
	public final Object getStructuralProperty(ASPropertyDescriptor property) {
		if (property instanceof ASSimplePropertyDescriptor) {
			ASSimplePropertyDescriptor p = (ASSimplePropertyDescriptor) property;
			if (p.getValueType() == int.class) {
				int result = internalGetSetIntProperty(p, true, 0);
				return new Integer(result);
			} else if (p.getValueType() == boolean.class) {
				boolean result = internalGetSetBooleanProperty(p, true, false);
				return Boolean.valueOf(result);
			} else {
				return internalGetSetObjectProperty(p, true, null);
			}
		}
		if (property instanceof ASChildPropertyDescriptor) {
			return internalGetSetChildProperty(
					(ASChildPropertyDescriptor) property, true, null);
		}
		if (property instanceof ASChildListPropertyDescriptor) {
			return internalGetChildListProperty((ASChildListPropertyDescriptor) property);
		}
		throw new IllegalArgumentException();
	}

	/**
	 * Sets the value of the given structural property for this node. The value
	 * passed depends on the kind of property:
	 * <ul>
	 * <li>{@link SimplePropertyDescriptor} - the new value of the given simple
	 * property, or <code>null</code> if none; primitive values are "boxed"</li>
	 * <li>{@link ChildPropertyDescriptor} - the new child node (type
	 * <code>ASTNode</code>), or <code>null</code> if none</li>
	 * <li>{@link ChildListPropertyDescriptor} - not allowed</li>
	 * </ul>
	 * 
	 * @param property
	 *            the property
	 * @param value
	 *            the property value
	 * @exception RuntimeException
	 *                if this node does not have the given property, or if the
	 *                given property cannot be set
	 * @since 3.0
	 */
	public final void setStructuralProperty(ASPropertyDescriptor property,
			Object value) {
		if (property instanceof ASSimplePropertyDescriptor) {
			ASSimplePropertyDescriptor p = (ASSimplePropertyDescriptor) property;
			if (p.getValueType() == int.class) {
				int arg = ((Integer) value).intValue();
				internalGetSetIntProperty(p, false, arg);
				return;
			} else if (p.getValueType() == boolean.class) {
				boolean arg = ((Boolean) value).booleanValue();
				internalGetSetBooleanProperty(p, false, arg);
				return;
			} else {
				if (value == null && p.isMandatory()) {
					throw new IllegalArgumentException();
				}
				internalGetSetObjectProperty(p, false, value);
				return;
			}
		}
		if (property instanceof ASChildPropertyDescriptor) {
			ASChildPropertyDescriptor p = (ASChildPropertyDescriptor) property;
			ASTNode child = (ASTNode) value;

			internalGetSetChildProperty(p, false, child);
			return;
		}
		if (property instanceof ASChildListPropertyDescriptor) {
			throw new IllegalArgumentException(
					"Cannot set the list of child list property");
		}
	}

	/**
	 * Sets the value of the given int-valued property for this node. The
	 * default implementation of this method throws an exception explaining that
	 * this node does not have such a property. This method should be extended
	 * in subclasses that have at leasy one simple property whose value type is
	 * int.
	 * 
	 * @param property
	 *            the property
	 * @param get
	 *            <code>true</code> for a get operation, and
	 *            <code>false</code> for a set operation
	 * @param value
	 *            the new property value; ignored for get operations
	 * @return the value; always returns <code>0</code> for set operations
	 * @exception RuntimeException
	 *                if this node does not have the given property, or if the
	 *                given value cannot be set as specified
	 * @since 3.0
	 */
	int internalGetSetIntProperty(ASSimplePropertyDescriptor property,
			boolean get, int value) {
		throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
	}

	/**
	 * Sets the value of the given boolean-valued property for this node. The
	 * default implementation of this method throws an exception explaining that
	 * this node does not have such a property. This method should be extended
	 * in subclasses that have at leasy one simple property whose value type is
	 * boolean.
	 * 
	 * @param property
	 *            the property
	 * @param get
	 *            <code>true</code> for a get operation, and
	 *            <code>false</code> for a set operation
	 * @param value
	 *            the new property value; ignored for get operations
	 * @return the value; always returns <code>false</code> for set operations
	 * @exception RuntimeException
	 *                if this node does not have the given property, or if the
	 *                given value cannot be set as specified
	 * @since 3.0
	 */
	boolean internalGetSetBooleanProperty(ASSimplePropertyDescriptor property,
			boolean get, boolean value) {
		throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
	}

	/**
	 * Sets the value of the given property for this node. The default
	 * implementation of this method throws an exception explaining that this
	 * node does not have such a property. This method should be extended in
	 * subclasses that have at leasy one simple property whose value type is a
	 * reference type.
	 * 
	 * @param property
	 *            the property
	 * @param get
	 *            <code>true</code> for a get operation, and
	 *            <code>false</code> for a set operation
	 * @param value
	 *            the new property value, or <code>null</code> if none;
	 *            ignored for get operations
	 * @return the value, or <code>null</code> if none; always returns
	 *         <code>null</code> for set operations
	 * @exception RuntimeException
	 *                if this node does not have the given property, or if the
	 *                given value cannot be set as specified
	 * @since 3.0
	 */
	Object internalGetSetObjectProperty(ASSimplePropertyDescriptor property,
			boolean get, Object value) {
		throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
	}

	/**
	 * Sets the child value of the given property for this node. The default
	 * implementation of this method throws an exception explaining that this
	 * node does not have such a property. This method should be extended in
	 * subclasses that have at leasy one child property.
	 * 
	 * @param property
	 *            the property
	 * @param get
	 *            <code>true</code> for a get operation, and
	 *            <code>false</code> for a set operation
	 * @param child
	 *            the new child value, or <code>null</code> if none; always
	 *            <code>null</code> for get operations
	 * @return the child, or <code>null</code> if none; always returns
	 *         <code>null</code> for set operations
	 * @exception RuntimeException
	 *                if this node does not have the given property, or if the
	 *                given child cannot be set as specified
	 */
	ASTNode internalGetSetChildProperty(ASChildPropertyDescriptor property,
			boolean get, ASTNode child) {
		throw new RuntimeException("Node does not have this property");
	}

	/**
	 * Returns the list value of the given property for this node. The default
	 * implementation of this method throws an exception explaining that this
	 * noed does not have such a property. This method should be extended in
	 * subclasses that have at leasy one child list property.
	 * 
	 * @param property
	 *            the property
	 * @return the list (element type: {@link ASTNode})
	 * @exception RuntimeException
	 *                if the given node does not have the given property
	 * 
	 */
	List<ASTNode> internalGetChildListProperty(
			ASChildListPropertyDescriptor property) {
		throw new RuntimeException("Node does not have this property");
	}

	/**
	 * Internal helper method that starts the building a list of property
	 * descriptors for the given node type.
	 * 
	 * @param nodeClass
	 *            the class for a concrete node type
	 * @param propertyList
	 *            empty list
	 */
	static void createPropertyList(Class nodeClass, List<Object> propertyList) {
		// stuff nodeClass at head of list for future ref
		propertyList.add(nodeClass);
	}

	/**
	 * Internal helper method that adding a property descriptor.
	 * 
	 * @param property
	 *            the structural property descriptor
	 * @param propertyList
	 *            list beginning with the AST node class followed by accumulated
	 *            structural property descriptors
	 */
	static void addProperty(ASPropertyDescriptor property,
			List<Object> propertyList) {
		Class nodeClass = (Class) propertyList.get(0);
		if (property.getNodeClass() != nodeClass) {
			// easily made cut-and-paste mistake
			throw new RuntimeException(
					"Structural property descriptor has wrong node class!"); //$NON-NLS-1$
		}

		propertyList.add(property);
	}

	/**
	 * Internal helper method that completes the building of a node type's
	 * structural property descriptor list.
	 * 
	 * @param propertyList
	 *            list beginning with the AST node class followed by accumulated
	 *            structural property descriptors
	 * @return unmodifiable list of structural property descriptors (element
	 *         type: <code>StructuralPropertyDescriptor</code>)
	 */
	static List<ASPropertyDescriptor> reapPropertyList(List<Object> propertyList) {
		propertyList.remove(0); // remove nodeClass
		// compact
		ArrayList<ASPropertyDescriptor> a = new ArrayList<ASPropertyDescriptor>(
				propertyList.size());
		for (Object p : propertyList) {
			a.add((ASPropertyDescriptor) p);
		}
		return Collections.unmodifiableList(a);
	}

	//
	// ======== INTERNAL LIST EVENT HANDLERS
	//

	/**
	 * Invoked before a child is added to a child node list.
	 * 
	 * If you override this method, be sure to call the superclass'
	 * implementation.
	 */
	protected void preAddListChild(ASTNode child,
			ASChildListPropertyDescriptor property) {
		ast.preAddChildEvent(this, child, property);
	}

	/**
	 * Invoked after a child is added to a child node list.
	 * 
	 * If you override this method, be sure to call the superclass'
	 * implementation.
	 */
	protected void postAddListChild(ASTNode child,
			ASChildListPropertyDescriptor property) {
		ast.postAddChildEvent(this, child, property);
	}

	/**
	 * Invoked before a child is removed from a child node list.
	 * 
	 * If you override this method, be sure to call the superclass'
	 * implementation.
	 */
	protected void preRemoveListChild(ASTNode child,
			ASChildListPropertyDescriptor property) {
		ast.preRemoveChildEvent(this, child, property);
	}

	/**
	 * Invoked after a child is removed from a child node list.
	 * 
	 * If you override this method, be sure to call the superclass'
	 * implementation.
	 */
	protected void postRemoveListChild(ASTNode child,
			ASChildListPropertyDescriptor property) {
		ast.postRemoveChildEvent(this, child, property);
	}

	/**
	 * Invoked before a child is removed from a child node list.
	 * 
	 * If you override this method, be sure to call the superclass'
	 * implementation.
	 */
	protected void preReplaceListChild(ASTNode oldChild, ASTNode newChild,
			ASChildListPropertyDescriptor property) {
		ast.preReplaceChildEvent(this, oldChild, newChild, property);
	}

	/**
	 * Invoked after a child is removed from a child node list.
	 * 
	 * If you override this method, be sure to call the superclass'
	 * implementation.
	 */
	protected void postReplaceListChild(ASTNode oldChild, ASTNode newChild,
			ASChildListPropertyDescriptor property) {
		ast.postReplaceChildEvent(this, oldChild, newChild, property);
	}

	//
	// ======== COLLECTION
	//

	/**
	 * An array list that dispatches events when changes are made to the
	 * underlying collection.
	 */
	public class NodeList extends AbstractList<ASTNode> {

		/**
		 * The serial version ID.
		 */
		private static final long serialVersionUID = -6362697882967194241L;

		/**
		 * Property descriptor.
		 */
		ASChildListPropertyDescriptor propertyDescriptor;

		/**
		 * List used internally.
		 */
		List<ASTNode> internalList = new ArrayList<ASTNode>();

		//
		// ======== CONSTRUCTION
		//

		/**
		 * Constructs a new list.
		 */
		public NodeList(ASChildListPropertyDescriptor property) {
			super();

			this.propertyDescriptor = property;
		}

		// ===== OVERRIDES

		@Override
		public int size() {
			return internalList.size();
		}

		@Override
		public ASTNode get(int index) {
			return internalList.get(index);
		}

		@Override
		public void add(int index, ASTNode newChild) {
			if (newChild == null) {
				throw new IllegalArgumentException();
			}
			if (ASTNode.this.protect) {
				// this node is protected => cannot gain or lose children
				throw new IllegalArgumentException(
						"AST node cannot be modified");
			}
			// link new child to parent
			ASTNode.checkNewChild(ASTNode.this, newChild,
					this.propertyDescriptor.elementType);
			ASTNode.this.preAddListChild(newChild, this.propertyDescriptor);

			internalList.add(index, newChild);

			newChild.setParent(ASTNode.this, this.propertyDescriptor);
			ASTNode.this.postAddListChild(newChild, this.propertyDescriptor);
		}

		@Override
		public ASTNode remove(int index) {
			if (ASTNode.this.protect) {
				// this node is protected => cannot gain or lose children
				throw new IllegalArgumentException(
						"AST node cannot be modified"); //$NON-NLS-1$
			}
			// delink old child from parent
			ASTNode oldChild = get(index);
			if (oldChild.protect) {
				// old child is protected => cannot be unparented
				throw new IllegalArgumentException(
						"AST node cannot be modified");
			}

			ASTNode.this.preRemoveListChild(oldChild, this.propertyDescriptor);
			// n.b. setParent will call ast.modifying()
			oldChild.setParent(null, null);
			ASTNode result = this.internalList.remove(index);
			ASTNode.this.postRemoveListChild(oldChild, this.propertyDescriptor);
			return result;
		}

		@Override
		public ASTNode set(int index, ASTNode element) {
			if (element == null) {
				throw new IllegalArgumentException();
			}

			if (ASTNode.this.protect) {
				// this node is protected => cannot gain or lose children
				throw new IllegalArgumentException(
						"AST node cannot be modified");
			}

			ASTNode old = get(index);
			if (old == element) {
				return old;
			}

			if (old.protect) {
				// old child is protected => cannot be unparented
				throw new IllegalArgumentException(
						"AST node cannot be modified");
			}

			ASTNode.checkNewChild(ASTNode.this, element,
					this.propertyDescriptor.elementType);
			ASTNode.this.preReplaceListChild(old, element,
					this.propertyDescriptor);

			ASTNode result = super.set(index, element);

			old.setParent(null, null);
			element.setParent(ASTNode.this, this.propertyDescriptor);
			ASTNode.this.postReplaceListChild(old, element,
					this.propertyDescriptor);

			return result;
		}
	}
}
